﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BeanboxSoftware.BeanMap
{
  public partial class Map<K1, K2, K3, V> : Internal.MapBase<Func<K1, K2, K3, V>, V, K1, IDictionary<K2, IDictionary<K3, V>>>, IMap<K1, K2, K3, V>
  {
    public Map()
    {
    }

    public Map(IEnumerable<V> list, Func<V, K1> key1Gen, Func<V, K2> key2Gen, Func<V, K3> key3Gen)
    {
      foreach (var item in list)
      {
        this[key1Gen(item), key2Gen(item), key3Gen(item)] = item;
      }
    }

    public Map(Map<K1, K2, K3, V> copy)
    {
      foreach (var kv in copy.KeyValues)
      {
        this[kv.Key.Item1, kv.Key.Item2, kv.Key.Item3] = kv.Value;
      }
    }

    public IMap<K2, K3, V> this[K1 k1]
    {
      get
      {
        return new Internal.MapBase<Func<K2, K3, V>, V, K2, IDictionary<K3, V>>._PassthroughFactory().CreatePassthrough<Map<K2, K3, V>>(
          build => ContainsKey(k1) ? RetrieveDataHandle()[k1] : build ? Prepare(k1) : null,
          () => RemovalCleanup(k1),
          (k2, k3) => this[k1, k2, k3]);
      }
    }

    public IMap<K3, V> this[K1 k1, K2 k2]
    {
      get
      {
        return new Internal.MapBase<Func<K3, V>, V, K3, V>._PassthroughFactory().CreatePassthrough<Map<K3, V>>(
          build => ContainsKey(k1, k2) ? RetrieveDataHandle()[k1][k2] : build ? Prepare(k1, k2) : null,
          () => RemovalCleanup(k1, k2),
          (k3) => this[k1, k2, k3]);
      }
    }

    protected IDictionary<K2, IDictionary<K3, V>> Prepare(K1 k1)
    {
      var data = SafeGet();

      if (!data.ContainsKey(k1))
      {
        data[k1] = new Dictionary<K2, IDictionary<K3, V>>();
      }

      return data[k1];
    }

    protected IDictionary<K3, V> Prepare(K1 k1, K2 k2)
    {
      var data = Prepare(k1);

      if (!data.ContainsKey(k2))
      {
        data[k2] = new Dictionary<K3, V>();
      }

      return data[k2];
    }

    protected void SafeSet(K1 k1, K2 k2, K3 k3, V value)
    {
      var data = Prepare(k1, k2);
      data[k3] = value;
    }

    public V this[K1 k1, K2 k2, K3 k3]
    {
      get
      {
        if (!ContainsKey(k1, k2, k3))
        {
          return HandleKeyNotFound(gen => gen(k1, k2, k3), v => this[k1, k2, k3] = v, () => new object[] { k1, k2, k3 });
        }

        return RetrieveDataHandle()[k1][k2][k3];
      }
      set
      {
        SafeSet(k1, k2, k3, value);
      }
    }

    public bool ContainsKey(K1 k1, K2 k2, K3 k3)
    {
      var data = RetrieveDataHandle();

      return
        data.ContainsKey(k1) &&
        data[k1].ContainsKey(k2) &&
        data[k1][k2].ContainsKey(k3);
    }

    public bool ContainsKey(K1 k1, K2 k2)
    {
      var data = RetrieveDataHandle();

      return
        data.ContainsKey(k1) &&
        data[k1].ContainsKey(k2);
    }

    public override int Count
    {
      get { return RetrieveDataHandle().Sum(kvp => kvp.Value.Sum(kkvp => kkvp.Value.Count)); }
    }

    public IEnumerator<KeyValuePair<Tuple<K1, K2, K3>, V>> GetEnumerator()
    {
      return KeyValues.GetEnumerator();
    }

    public IEnumerable<KeyValuePair<Tuple<K1, K2, K3>, V>> KeyValues
    {
      get
      {
        var data = RetrieveDataHandle();

        foreach (var k1 in data.Keys)
        {
          foreach (var k2 in data[k1].Keys)
          {
            foreach (var k3 in data[k1][k2].Keys)
            {
              yield return new KeyValuePair<Tuple<K1, K2, K3>, V>(Tuple.Create(k1, k2, k3), data[k1][k2][k3]);
            }
          }
        }
      }
    }

    public IEnumerable<Tuple<K1, K2, K3>> Keys
    {
      get
      {
        return KeyValues.Select(kvs => kvs.Key);
      }
    }

    public IEnumerable<V> Values
    {
      get
      {
        return KeyValues.Select(kvs => kvs.Value);
      }
    }

    public override bool TryGetValue(K1 key, out IDictionary<K2, IDictionary<K3, V>> value)
    {
      value = this[key];
      return true;
    }

    protected override IDictionary<K2, IDictionary<K3, V>> IndexerGet(K1 key)
    {
      return this[key];
    }

    public bool Remove(K1 k1, K2 k2)
    {
      if (!ContainsKey(k1))
      {
        return false;
      }

      var data = RetrieveDataHandle();
      if (data[k1].Remove(k2))
      {
        RemovalCleanup(k1);
        return true;
      }

      return false;
    }

    public bool Remove(K1 k1, K2 k2, K3 k3)
    {
      if (!ContainsKey(k1, k2))
      {
        return false;
      }

      var data = RetrieveDataHandle();

      if (data[k1][k2].Remove(k3))
      {
        RemovalCleanup(k1, k2);
        return true;
      }

      return false;
    }

    protected void RemovalCleanup(K1 k1)
    {
      var data = RetrieveDataHandle();
      if (data.ContainsKey(k1) && data[k1].Count == 0)
      {
        data.Remove(k1);
      }

      RemovalCleanup();
    }

    protected void RemovalCleanup(K1 k1, K2 k2)
    {
      var data = RetrieveDataHandle();
      if (data.ContainsKey(k1) && data[k1].ContainsKey(k2) && data[k1][k2].Count == 0)
      {
        data[k1].Remove(k2);
      }

      RemovalCleanup(k1);
    }

    public IMap<K2, K3, V> Get(K1 key)
    {
      return this[key];
    }

    public IMap<K3, V> Get(Tuple<K1, K2> key)
    {
      return this[key.Item1, key.Item2];
    }

    public IMap<K3, V> Get(K1 key1, K2 key2)
    {
      return this[key1, key2];
    }

    public V Get(Tuple<K1, K2, K3> key)
    {
      return this[key.Item1, key.Item2, key.Item3];
    }

    public V Get(K1 key1, K2 key2, K3 key3)
    {
      return this[key1, key2, key3];
    }

    public bool ContainsKey(Tuple<K1, K2, K3> key)
    {
      return ContainsKey(key.Item1, key.Item2, key.Item3);
    }

    public bool ContainsKey(Tuple<K1, K2> key)
    {
      return ContainsKey(key.Item1, key.Item2);
    }
  }
}
